Injections Samples
See Customization Basics and Injection API for more details on using API injections
Canvas
Adding canvas elements to the chart.
The following is a sample injection that shows how to draw different canvas elements, such as lines, text boxes and labels, at a specific price point and date coordinates.
This injection is attached to the "draw" method inside the chart's animation loop. It will therefore render when the user moves the chart back and forth. This is critical because the y-axis location of a certain price will change depending on the zooming and scrolling of the user. Also, since this is in the animation loop we are guaranteed to redraw the label whenever the actual values changes from new streaming data.
function injectionSample() {
var chart = this.chart,
panel = chart.panel,
yAxis = panel.yAxis,
xaxis = chart.xaxis,
dataSet = chart.dataSet,
ctx = chart.context;
var parameters = {
pattern: "dashed", // options: "solid","dashed","dotted"
lineWidth: 2 // select any width for the line in pixels
};
if (!dataSet.length) return;
/*** display a horizontal line showing the current high price ***/
var price = dataSet[dataSet.length - 1].High;
var x = this.pixelFromDate(dataSet[dataSet.length - 1].Date, chart);
var y = this.pixelFromPrice(price, panel);
this.plotLine(x, x + 1, y, y, "orange", "horizontal", ctx, true, parameters);
/*** display label on the y-axis showing the current high price ***/
var txt = price;
if (chart.transformFunc) {
txt = chart.transformFunc(this, chart, price);
}
txt = yAxis.priceFormatter
? yAxis.priceFormatter(this, panel, txt)
: this.formatYAxisPrice(txt, panel);
this.createYAxisLabel(panel, txt, y, "orange");
/*** write a text message under the current high price ***/
// put a nice colored background border under the text
this.startClip(panel.name); // ensure the drawing doesn't extend outside of the panel
ctx.fillStyle = "gray"; // fill color
ctx.strokeStyle = "orange"; // border color
ctx.lineWidth = 2; // optional border width
ctx.strokeRect(x - 202, y - 30, 220, 21);
ctx.fillRect(x - 202, y - 30, 220, 21);
this.endClip();
// print text above line
this.startClip(panel.name); // ensure the drawing doesn't extend outside of the panel
ctx.font = "14pt Calibri";
ctx.fillStyle = "orange";
ctx.fillText("This is the current high price", x - 200, y - 20);
this.endClip();
/*** draw a vertical line next to the text message ***/
//draw line using native canvas calls
this.startClip(panel.name); // ensure the drawing doesn't extend outside of the panel
ctx.beginPath();
ctx.moveTo(x - 202, this.pixelFromPrice(0, panel));
ctx.lineTo(x - 202, this.pixelFromPrice(400, panel));
ctx.lineWidth = 2;
ctx.setLineDash([2, 2]); //creates dotted line, array values are pixels on, pixels off
ctx.strokeStyle = "green";
ctx.stroke();
this.endClip();
/*** display a vertical line at the end of the market day only for intraday charts ***/
if (!CIQ.ChartEngine.isDailyInterval(this.layout.interval)) {
var lastVisibleDate = xaxis[xaxis.length - 1].DT; // these are in dataZone
var firstVisibleDate = xaxis[0].DT; // these are in dataZone
var now = new Date();
// when working with the chart data, we must make sure the times are converted to the dataZone timezone. If there is a datazone set, convert the browser time to the data time zone.
if (this.dataZone) {
// convert the current time to the dataZone
var tzNow = CIQ.convertTimeZone(now, null, this.dataZone);
now = new Date(
tzNow.getFullYear(),
tzNow.getMonth(),
tzNow.getDate(),
tzNow.getHours(),
tzNow.getMinutes(),
tzNow.getSeconds(),
tzNow.getMilliseconds()
);
}
if (!chart.market.isOpen()) now = chart.market.getPreviousOpen(now);
var currentClose = chart.market.getPreviousClose(now);
if (currentClose >= firstVisibleDate && currentClose <= lastVisibleDate) {
// the current close is visible in the chart...
x = this.pixelFromDate(CIQ.yyyymmddhhmm(currentClose), chart);
this.plotLine(x, x, 0, 0, "red", "vertical", ctx, true, parameters);
}
}
}
CIQ.ChartEngine.prototype.append("draw", injectionSample);
Drawing average lines for series on the canvas
The following is a sample injection that shows how to draw 'average lines' for every additional series added to the chart. A y-axis floating label will also be added to display the average value. To enable the average lines, you can add UI to set stxx.averageLines=true;
, and then set to stxx.averageLines=false;
to once again disable.
function averageLines() {
function avg(symbol) {
var sum = 0;
for (var i = 0; i < dataSegment.length; i++) {
sum += dataSegment[i][symbol] ? dataSegment[i][symbol].Close : 0;
}
return sum / dataSegment.length;
}
if (!stxx.averageLines) return;
var parameters = {
//pattern: "dashed", // options: "solid","dashed","dotted"
//lineWidth: 2 // select any width for the line in pixels
};
var chart = this.chart,
xaxis = chart.xaxis,
dataSegment = chart.dataSegment,
ctx = chart.context;
if (!dataSegment.length) return;
for (n in chart.seriesRenderers) {
var renderer = chart.seriesRenderers[n],
params = renderer.params,
panelName = params.panel,
panel = this.panels[panelName];
var yAxis = renderer.params.yAxis || panel.yAxis;
if (params.name == "_main_series") continue;
for (var m = 0; m < renderer.seriesParams.length; m++) {
var sParams = renderer.seriesParams[m];
var symbol = sParams.symbol;
/**************** display a horizontal line showing the current high price *****************/
var average = avg(symbol);
var y = this.pixelFromPrice(average, panel, yAxis);
this.plotLine(0, 1, y, y, sParams.color, "horizontal", ctx, true, parameters);
/***************** display label on the y-axis showing the current high price ***************/
var txt = average;
if (chart.transformFunc) {
txt = chart.transformFunc(this, chart, average);
}
txt = yAxis.priceFormatter
? yAxis.priceFormatter(this, panel, txt)
: this.formatYAxisPrice(txt, panel);
this.createYAxisLabel(panel, txt, y, sParams.color, null, null, yAxis);
}
}
}
CIQ.ChartEngine.prototype.append("draw", averageLines);
Canvas alternative to HTML markers
function markerInjection() {
var markerList = [
new Date(2015, 9, 15, 15, 12),
new Date(2015, 9, 20, 9, 00),
new Date(2015, 10, 30, 12, 30),
new Date(2015, 10, 30, 13, 45),
new Date(2015, 10, 30, 14, 30),
new Date(2015, 10, 30, 15, 30),
new Date(2015, 10, 30, 16, 30),
new Date(2015, 10, 30, 17, 30)
];
var chart = this.chart,
dataSegment = chart.dataSegment,
panel = chart.panel,
ctx = chart.context;
if (!dataSegment.length) return;
this.startClip(panel.name); // save current context
for (var j = 0; j < markerList.length; j++) {
for (var i = dataSegment.length - 1; i >= 0; i--) {
// find the tick you want this on
if (dataSegment[i].DT <= markerList[j]) {
// this code finds the actual tick or the one right before to put the marker on.
var useHighs = this.chart.highLowBars;
var price = useHighs ? dataSegment[i].High : dataSegment[i].Close;
var x = this.pixelFromDate(dataSegment[i].Date);
// place 20 pixels over the line/candle
var y = this.pixelFromPrice(price, panel) - 20;
// draw a circle
ctx.beginPath();
ctx.lineWidth = 1;
var radius = 12;
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = "green";
ctx.strokeStyle = "black";
ctx.fill();
ctx.stroke();
ctx.closePath();
//write leter in the circle
ctx.font = "10pt Calibri";
ctx.fillStyle = "white";
ctx.fillText("T", x - 4, y);
break;
}
}
}
this.endClip(); // restore previous context so all is back how it was.
}
CIQ.ChartEngine.prototype.append("draw", markerInjection);
X-axis manipulations
Format the crosshairs floating x-axis label
stxx.chart.xAxis.formatter formats both the x-axis labels and the crosshairs' floating x-axis label (HUD). This injection provides a means to override the crosshairs' floating x-axis label only.
CIQ.ChartEngine.prototype.append("headsUpHR", function() {
console.log(this.currentPanel.chart.xaxis[this.barFromPixel(this.cx)]); // Original date object.
console.log("Formatted date: " + this.controls.floatDate.innerHTML); // Formatted date.
//Custom formatting code here...
this.controls.floatDate.innerHTML = "Custom " + this.controls.floatDate.innerHTML; // Additional formatting prior to display in HUD.
});
Disabling x-axis vertical zooming
By default, if you click and drag over the x-axis, the chat will zoom horizontally. This override disables that functionality on both touch and mouse devices.
Please note, this is not a bona fide injection, but rather a function override. They work similarly and can be leveraged in cases when an injection to a particular function is not available.
stxx.origZoomSet = stxx.zoomSet;
CIQ.ChartEngine.prototype.zoomSet = function(candleWidth, chart) {
var mainSeriesRenderer = this.mainSeriesRenderer;
if (!mainSeriesRenderer.params || !mainSeriesRenderer.params.volume) {
if (this.grabMode == "zoom-x") {
chart.spanLock = false;
return;
} else {
stxx.origZoomSet(candleWidth, chart);
}
}
};
Y-axis manipulations
Suppressing the y-axis auto adjust as chart is panned left or right
By default, the chart will auto adjust (zoom in or out the y-axis) to display all candles for the viewing period . If you wish for your chart to continue using the initial y-axis zoom level automatically determined at initial chart load, and allow ticks to fall off the chart on the top or the bottom, you can add the following prepend and append functions to maintain that level:
CIQ.ChartEngine.prototype.prepend("initializeDisplay", function(chart) {
currentGlobalLow = chart.lowValue;
currentGlobalHigh = chart.highValue;
});
CIQ.ChartEngine.prototype.append("initializeDisplay", function(chart) {
if (typeof activeSymbol == "undefined") activeSymbol = chart.symbol;
else if (activeSymbol == chart.symbol) {
if (currentGlobalLow) chart.lowValue = currentGlobalLow;
if (currentGlobalHigh) chart.highValue = currentGlobalHigh;
} else {
activeSymbol = chart.symbol;
}
});
Setting a static range for the y-axis
If you wish to force a specific y-axis range for the chart instead of the default variable range based on the data, you can use this injection:
var changeAxis = function() {
this.chart.yAxis.lowValue = 50; // set to the lower bound
this.chart.yAxis.highValue = 100; // set to the higher bound
};
stxx.append("initializeDisplay", changeAxis);
Disabling y-axis vertical zooming
By default, if you click (or touch) and drag over the y-axis, the chart will zoom vertically.
This override disables that functionality both on mouse and touch devices:
CIQ.ChartEngine.prototype.prepend("mousemoveinner", function () {
this.grabStartYAxis=0;
});
This override disables that functionality on mouse devices only:
CIQ.ChartEngine.prototype.prepend("mousemove", function () {
this.grabStartYAxis=0;
});
This override disables that functionality on touch devices only:
CIQ.ChartEngine.prototype.append("touchmove", function() {
this.grabStartYAxis = 0;
});
Sync up secondary-axis to primary chart on main panel
Example 1:
Sometimes you want to have a secondary series that has a different price range on a separate y-axis, but you do not want the secondary series to maintain its own range. Instead, you prefer it to follow the range of the main series, even if this causes it to go off screen. The result is true-to-life scale where the primary series determines the range instead of otherwise shrinking (flattening) the primary series to ensure the secondary series is also fully in range.
CIQ.ChartEngine.prototype.prepend("createYAxis", function(panel, parameters) {
if (secondaryAxis.name == parameters.yAxis.name) {
secondaryAxis.zoom = this.chart.yAxis.zoom;
secondaryAxis.max = this.chart.yAxis.high;
secondaryAxis.min = this.chart.yAxis.low;
this.calculateYAxisRange(
panel,
secondaryAxis,
this.chart.yAxis.lowValue,
this.chart.yAxis.highValue
);
}
});
var secondaryAxis = new CIQ.ChartEngine.YAxis();
secondaryAxis.width = 0; // hide it since the primary drives the chart and will always match
var renderer = stxx.setSeriesRenderer(
new CIQ.Renderer.Lines({
params: { name: "lines", yAxis: secondaryAxis }
})
);
// make sure you call addSeries to add your data first.
renderer.attachSeries(scondarySeriesSymbol, "#FFBE00").ready();
Example 2:
Alternatively, you may want to have two axes in different scales for the primary symbol.
In this example there will be a left axis showing actual values (linear scale), and a right axis for the same primary symbol showing percentage change.
This is done by setting the primary series scale to 'percentage', and then adding a secondary series for the same primary instrument in linear scale. Once both series (each one with its on axis in the desired scale) are rendered, their y-axis are linked with the injection so they stay in sync as the user zooms and pans.
// define this once only
CIQ.ChartEngine.prototype.prepend("createYAxis", function(panel, parameters) {
if (!stxx.chart.series[stxx.chart.symbol]) return;
var secondaryAxis = stxx.chart.series[stxx.chart.symbol].parameters.yAxis;
if (secondaryAxis.name == parameters.yAxis.name) {
secondaryAxis.zoom = this.chart.yAxis.zoom;
secondaryAxis.max = this.chart.untransformFunc
? this.chart.untransformFunc(this, this.chart, this.chart.yAxis.high)
: this.chart.yAxis.high;
secondaryAxis.min = this.chart.untransformFunc
? this.chart.untransformFunc(this, this.chart, this.chart.yAxis.low)
: this.chart.yAxis.low;
this.calculateYAxisRange(
panel,
secondaryAxis,
this.chart.untransformFunc
? this.chart.untransformFunc(this, this.chart, this.chart.yAxis.lowValue)
: this.chart.yAxis.lowValue,
this.chart.untransformFunc
? this.chart.untransformFunc(this, this.chart, this.chart.yAxis.highValue)
: this.chart.yAxis.highValue
);
}
});
// do this for every new instrument loaded.
stxx.setChartScale("percent");
stxx.addSeries(stxx.chart.symbol, { color: "transparent", yAxis: { position: "left" } });
Note that the prepend injection should only be defined once. So move it as needed to prevent it from being defined over and over again.
Floating price labels manipulations
Current price label to follow the visible candle
By default, the current price label will always display the last (current) price for that instrument, even that last bar is not visible.
If you wish to override this behavior and have the label display the value of the last 'visible' candle you can do the following:
- Turn off default label:
stxx.chart.yAxis.drawCurrentPriceLabel = false;
- Add an API injection to create a label match the last visible candle:
stxx.append("draw", function() {
var visibleQuotes = this.getDataSegment();
var last = visibleQuotes[visibleQuotes.length - 1].Close;
if (last) {
var chart = this.chart;
var panel = chart.panel;
var yAxis = panel.yAxis;
if (panel.hidden) return;
if (chart.transformFunc) currentClose = chart.transformFunc(this, this.chart, last);
var txt;
var labelDecimalPlaces = Math.max(
panel.yAxis.printDecimalPlaces,
panel.chart.decimalPlaces
);
if (yAxis.maxDecimalPlaces || yAxis.maxDecimalPlaces === 0)
labelDecimalPlaces = Math.min(labelDecimalPlaces, yAxis.maxDecimalPlaces);
if (yAxis.priceFormatter) {
txt = yAxis.priceFormatter(this, panel, last, labelDecimalPlaces);
} else {
txt = this.formatYAxisPrice(last, panel, labelDecimalPlaces);
}
var y = this.pixelFromTransformedValue(txt, chart.panel);
// *** select the label background and text colors. We use 'blue' and 'white' respectively. ***
this.createYAxisLabel(panel, txt, y, "blue", "white");
}
});
Spreading y-axis labels to prevent overlapping
The following is a very advanced compounded customization that relies on several injections and function overrides. It is designed to illustrate how combining these different methods can allow you to dramatically customize the library to achieve your desired look and feel.
This code modifies the default behavior of the chart by spreading labels to prevent overlaps, instead of placing them in their actual price position.
It basically collects the label potions into an array first, instead of drawing them as they come. It then sorts them out, separates them into lists for the upper section and lower sections and spreads them up and down using the current price label as anchor to allow the current price label to be in its original position.
This sample assumes a single y-axis, but you can further break out each y-axis data set form the list and spread separately as needed using the same spreadUp
and spreadDown
functions.
var listOfLabels;
CIQ.ChartEngine.prototype.updateFloatHRLabel = function() {}; // turn of the y-axis floating label to prevent conflicts.
stxx.prepend("draw", function() {
listOfLabels = { current: null, series: [] };
stxx.yaxisLabelStyle = "roundRectArrow2";
});
stxx.prepend("drawCurrentHR", function() {
listOfLabels.drawingCurrentHR = true;
});
CIQ.roundRectArrow2 = function(params) {
// This injection collects all of the labels to be drawn on the y-axis
if (listOfLabels.drawingCurrentHR) {
listOfLabels.drawingCurrentHR = false;
listOfLabels.current = { params: params };
CIQ.roundRect(params, "arrow");
} else {
listOfLabels.series.push({ params: params });
}
};
stxx.append("draw", function() {
// This injection does all the heavy lifting.
// It iterates through the list of labels and decides if they need to be spread up or down.
// The current price label acts as the anchor and labels are moved up or down from it.
listOfLabels.series.sort(function(a, b) {
return a.params.top + a.params.height - b.params.top;
});
spreadDown = function(list) {
for (i = 1; i < list.length; i++) {
var previousBottom = list[i - 1].params.top + list[i - 1].params.height;
var currentTop = list[i].params.top;
if (currentTop < previousBottom) {
var adjustment = previousBottom - currentTop;
list[i].params.top += adjustment;
list[i].params.y += adjustment;
}
}
for (var i = 1; i < list.length; i++) CIQ.roundRect(list[i].params, "arrow");
};
spreadUp = function(list) {
for (i = list.length - 2; i >= 0; i--) {
var previousTop = list[i + 1].params.top;
var currentBottom = list[i].params.top + list[i].params.height;
if (previousTop < currentBottom) {
var adjustment = currentBottom - previousTop;
list[i].params.top -= adjustment;
list[i].params.y -= adjustment;
}
}
for (var i = 0; i < list.length - 1; i++) CIQ.roundRect(overCurrent[i].params, "arrow");
};
// This sample assumes a single axis and simply illustrates
// how you can create a list of labels and spread them up and down to prevent overlapping.
// If multiple axis are used, additional code will need to be added to further separate into individual axis lists
// using the 'params.x' as the key.
// Once separated by-axis, spreadUp and spreadDown should be called for every-axis.
var overCurrent = [],
underCurrent = [listOfLabels.current];
for (var i = 0; i < listOfLabels.series.length; i++) {
if (listOfLabels.series[i].params.top > listOfLabels.current.params.top)
underCurrent.push(listOfLabels.series[i]);
else overCurrent.push(listOfLabels.series[i]);
}
overCurrent.push(listOfLabels.current);
spreadUp(overCurrent);
spreadDown(underCurrent);
stxx.yaxisLabelStyle = "roundRectArrow";
});
Zoom and scroll manipulations
Shrink chart as new candles are added instead of ticking back
By default, once there are enough candles to fill the chart, if in the home position (current var displayed), the chart will start ticking back so the current bar can still be visible. But since the width of the bars don’t change, the older bars will start going out of view. If your implementation requires the current range to remain visible even as new bars are added, this requires the bars to continuously get thinner to allow for more data in the same space, producing a shrinking effect. This injection will produce this effect:
stxx.prepend("createDataSet", function() {
if (!this.chart.dataSet || this.chart.dataSet.length == 0) return;
this.last = this.chart.dataSet[this.chart.dataSet.length - 1].DT;
console.log("last candle date before creating a new data set is", this.last);
});
stxx.prepend("draw", function() {
if (
this.layout.candleWidth > stxx.minimumCandleWidth &&
this.isHome() &&
this.last &&
this.last < this.chart.dataSet[this.chart.dataSet.length - 1].DT
) {
console.log(
"ready to draw. original scroll:",
this.chart.maxTicks,
this.chart.scroll,
this.micropixels
);
this.setMaxTicks(this.chart.maxTicks + 1);
this.chart.scroll++;
console.log("new scroll", this.chart.maxTicks, this.chart.scroll, this.micropixels);
} else {
console.log("keep the scroll, no new candles or candle can't get smaller");
}
this.last = this.chart.dataSet[this.chart.dataSet.length - 1].DT;
});
Shrink chart and scroll left-to-right as new candles are added
Normally, as new bars are added, the chart will scroll back from right to left. So the most current bar is flushed to the right edge, and as more bars are added, the left ticks will continue to move back and eventually go out of view.
This injection overrides that behavior so the oldest tick is flushed to the left edge of the chart and as new bars are added, the chart will grow towards the right. Once enough bars have filled the view window, instead of going out of view, the chart will continue to be compressed with each new bar, so all data is always visible.
This override is aimed at charts that will initially start empty or very little bars, and gradually add more data over time.
stxx.prepend("updateChartData", function() {
var dataSize = this.chart.dataSet.length + 1;
if (this.chart.maxTicks < dataSize + 3) {
this.setMaxTicks(dataSize + 2); // if full screen, squeeze chart as new candles come in
}
this.chart.scroll = dataSize + 1; // flush right
this.micropixels = 0;
});
To prevent user interaction from interfering with this behavior, you may also want to disable zooming and panning with the following overrides:
stxx.allowZoom=false;
stxx.allowScroll=false;
Move chart forward when new data is added
This injection will force the chart to move forward as new data is added instead of scrolling back. Once the right edge is reached, the chart will 'jump back' top its original position and start scrolling forward again.
stxx.prepend("createDataSet", function() {
if (!this.chart.dataSet || this.chart.dataSet.length == 0) return;
this.lastLength = this.chart.dataSet.length;
console.log("xxxxxxxxx last length before creating a new data set is", this.lastLength, this.chart.scroll);
});
stxx.append("createDataSet", function() {
console.log("yyyyyyy last length after creating a new data set is", this.lastLength, this.chart.scroll);
if( this.chart.scroll >= this.chart.maxTicks-2 ) {
console.log(
"<<<<< reached edge. gong back 100 pixels. original scroll",
this.chart.maxTicks,
this.chart.scroll,
this.micropixels
);
this.home();
} else if (
this.lastLength &&
this.lastLength != this.chart.dataSet.length
) {
console.log(
" >>>>> new candle. ready to move forward. original scroll:",
this.chart.maxTicks,
this.chart.scroll,
this.micropixels
);
this.chart.scroll++;
console.log("new scroll", this.chart.maxTicks, this.chart.scroll, this.micropixels);
} else {
console.log(" ==== keep the scroll, no new candles");
}
this.last = this.chart.dataSet[this.chart.dataSet.length - 1].DT;
});
Converting zoom mode into a scroll mode on touch devices
Sometimes the zoom feature can be cumbersome to use on some browsers, such as in small mobile devices. The following injection will convert the zooming into scrolling if using a touch device.
function disableZoom() {
if (CIQ.touchDevice) {
// fix the candlewidth (zoom level) at the desired size
this.setCandleWidth(18);
}
}
CIQ.ChartEngine.prototype.prepend("draw", disableZoom);
Note that if you wish to completely disable zooming you probably want to do so when you declare the chart using the allowZoom
parameter.
Example:
var stxx = new CIQ.ChartEngine({
container: document.querySelector(".chartContainer"),
allowZoom: false,
layout: { candleWidth: 16, crosshair: true }
});
Toggle touch and mouse events
CIQ.ChartEngine#manageTouchAndMouse is normally used to disable user interaction, making it a static chart. But sometimes you may want to temporarily enable interaction and then disable it again.
The following can toggle all events except the mouse wheel:
CIQ.ChartEngine.prototype.prepend("mousemoveinner", function() {
// add your logic to decide if you want the events handled or not...
// maybe via some sort of global variable, or CIQ.ChartEngine member variable....
// return true; //when you want to have the events disabled
// return false; //when you want to have the events enabled
});
Toggle mouse wheel events
The following only disables the mouse wheel:
CIQ.ChartEngine.prototype.prepend("mouseWheel", function(e) {
// add your logic to decide if you want the events handled or not
// ...maybe via some sort of global variable, or stxx.member variable....
//return true; //when you want to have the events disabled
//return false; //when you want to have the events enabled
});
Slowing down inertia on mouse wheel or mouse pad swipes
This injection will slow down the swiping and mouse wheel operations by ignoring requests that are too close from one another.
stxx.prepend("mouseWheel", function(e) {
var diff = null;
var timeLimit = 500;
if (this.lastMouseWheelEvent) diff = Date.now() - this.lastMouseWheelEvent;
if (diff && diff < timeLimit) {
console.log("skip");
return true;
}
console.log("go");
});
Adjust the timeLimit
as needed to achieve the interaction you are looking for.
Reverse scroll direction
Here is an example of an injection that scrolls bars right to left when building historical data one bar at a time. Normally the chart starts rendering candles on the left and moves towards the right with each new bar.
stxx.prepend("createDataSet", function() {
if (!stxx.chart.dataSet || stxx.chart.dataSet.length == 0) return;
if (stxx.isHome()) {
// Save the last date before the new data set is created.
stxx.last = stxx.chart.dataSet[stxx.chart.dataSet.length - 1].DT;
} else {
stxx.last = undefined;
}
});
stxx.prepend("draw", function() {
var indexNewLast = stxx.chart.dataSet.length - 1;
var newLast = stxx.chart.dataSet[indexNewLast].DT;
if (stxx.isHome() && stxx.last && stxx.last < newLast) {
// Find index "last" date
var indexOldLast;
for (var i = 0; i < stxx.chart.dataSet.length; ++i) {
if (stxx.chart.dataSet[i].DT >= stxx.last) {
indexOldLast = i;
break;
}
}
if (indexOldLast) {
// Add the difference to maintain the scroll position
stxx.chart.scroll += indexNewLast - indexOldLast;
}
}
});
Anchor chart at right edge while zooming
While zooming, anchor the bar on the right edge of the display so that the chart expands or contracts on the left edge only. By default, when the chart's most recent bar is not displayed, zooming will try to anchor the middle of the chart and expand/contract from both edges.
function setGrabMode() {
this.grabMode = "zoom-x";
}
stxx.prepend("zoomIn", setGrabMode);
stxx.prepend("zoomOut", setGrabMode);
function clearGrabMode() {
this.grabMode = "";
}
stxx.append("zoomIn", clearGrabMode);
stxx.append("zoomOut", clearGrabMode);
Maintaining height:width ratio upon resizing
CIQ.ChartEngine.prototype.prepend("resizeChart", function () {
let container = document.querySelector('#' + this.container.id);
if (this.heightWidthRatio) {
container.style.height = CIQ.elementDimensions(container).width * this.heightWidthRatio + "px";
}
}
Set the desired heightWidthRatio
as follows:
stxx.heightWidthRatio = 9 / 16;
Panel manipulations
Forcing a particular panel to remain at the bottom of the chart as new studies are added
CIQ.ChartEngine.prototype.prepend("adjustPanelPositions", function() {
var keepOnBottom = "volume"; // set the name of the panel here
var lastPanel;
var newPanels = {};
var pos = 0;
var p;
for (p in this.panels) {
if (p == keepOnBottom) {
lastPanel = this.panels[p];
break;
}
pos++;
}
if (!lastPanel) return;
var length = 0;
for (p in this.panels) length++;
if (pos == length - 1) return; //already at bottom
for (p in this.panels) {
if (p == lastPanel.name) continue;
newPanels[p] = this.panels[p];
}
newPanels[lastPanel.name] = lastPanel;
this.panels = newPanels;
});
Forcing a particular panel to remain at the top of the chart as new studies are added
This will prevent other panels to be placed over the primary panel, even if the user tries to click on the panel control buttons.
CIQ.ChartEngine.prototype.prepend("adjustPanelPositions", function() {
var keepOnTop = "chart"; // set the name of the panel here
var firstPanel;
var newPanels = {};
var pos = 0;
var p;
for (p in this.panels) {
if (p == keepOnTop) {
firstPanel = this.panels[p];
break;
}
pos++;
}
if (!firstPanel || pos == 0) return; //already at bottom or not there
newPanels[firstPanel.name] = firstPanel;
for (p in this.panels) {
if (p == firstPanel.name) continue;
newPanels[p] = this.panels[p];
}
this.panels = newPanels;
});
Crosshair and current quote manipulations
Add a dot to indicate the current quote
Here is an injection to display a dot (small circle) on the most current (last) tick for line and mountain charts. Select any color (default: 'blue') and size (default: 5) you want.
stxx.append("draw", function() {
if (
this.chart.dataSet &&
this.chart.dataSet.length &&
!this.chart.standaloneBars
) {
var context = this.chart.context;
var panel = this.chart.panel;
var currentQuote = this.currentQuote("Close");
if (!currentQuote) return;
var price = currentQuote.Close;
var x = this.pixelFromTick(currentQuote.tick, this.chart);
if (this.chart.lastTickOffset) x = x + this.chart.lastTickOffset;
var y = this.pixelFromPrice(price, panel);
this.startClip();
context.beginPath();
context.moveTo(x, y);
context.arc(x, y, 5, 0, Math.PI * 2, false);
context.fillStyle = "blue";
context.fill();
this.endClip();
}
});
Add a dot to highlight the mouse-over tick
Here is an injection to display a dot (small circle) as you are mousing over a particular tick on the chart.
CIQ.ChartEngine.prototype.append("mousemoveinner", function() {
var chart = this.chart;
var tick = this.tickFromPixel(this.cx);
var quote = chart.dataSet[tick];
chart.tempCanvas.style.display = "none";
if (this.overYAxis) return;
if (!quote) return;
if (quote.Close < stxx.chart.yAxis.low) return;
var x = this.pixelFromTick(tick);
var y = this.pixelFromPrice(quote.Close);
CIQ.clearCanvas(chart.tempCanvas, this);
var ctx = chart.tempCanvas.context;
ctx.beginPath();
ctx.lineWidth = 1;
ctx.arc(x, y, 4, 0, (2 * Math.PI), false);
ctx.strokeStyle = 'blue';
ctx.fillStyle = 'blue';
ctx.fill();
ctx.stroke();
ctx.closePath();
chart.tempCanvas.style.display = "block";
});
You may also want to turn off the horizontal crosshair line using this css override:
.crossY, .stx_crosshair_y {
display: none;
}
To also turn off the y-axis floating label, use this override:
CIQ.ChartEngine.prototype.updateFloatHRLabel = function (){};
Snap Y crosshair line to specific price intervals
This example snaps the Y crosshair to even numbers but you can customize it to snap to any price interval you may need. For example, certain instruments only trade in 0.25 dollar intervals. You can adjust this injection to do that. It is designed for mouse
interactions.
CIQ.ChartEngine.prototype.append("positionCrosshairsAtPointer", function() {
var chart = this.chart,
panel = chart.panel,
yAxis = panel.yAxis;
var price = this.priceFromPixel(this.crossYActualPos);
var difference = price % 2;
if (difference) {
this.crossYActualPos = this.pixelFromPrice(price - difference, panel);
if (yAxis.bottom < this.crossYActualPos) this.crossYActualPos = yAxis.bottom;
this.controls.crossY.style.top = this.crossYActualPos + "px";
}
});
The following is the touch
device comparable for the above injection, but done as a function override:
CIQ.ChartEngine.prototype.origMousemoveinner = CIQ.ChartEngine.prototype.mousemoveinner;
CIQ.ChartEngine.prototype.mousemoveinner = function (epX, epY) {
var chart = this.chart,
panel = chart.panel,
yAxis = panel.yAxis;
var crossYActualPos = this.backOutY(epY);
var price = this.priceFromPixel(crossYActualPos);
var difference = price % 2;
if (difference) {
crossYActualPos = this.pixelFromPrice(price - difference, panel);
if (yAxis.bottom < crossYActualPos) crossYActualPos = yAxis.bottom;
epY = this.resolveY(crossYActualPos);
}
return this.origMousemoveinner(epX, epY);
}
Regardless of the above examples, you can achieve the same thing using CIQ.ChartEngine.preferences.horizontalCrosshairField
Snap Y crosshair line to close price.
This is a variation of the "Snap Y crosshair line to specific price intervals" example. And can also be achieved using CIQ.ChartEngine.preferences.horizontalCrosshairField
// snap the y-axis
CIQ.ChartEngine.prototype.append("positionCrosshairsAtPointer", function() {
if (this.currentVectorParameters.vectorType) return; // don't override if drawing
var chart = this.chart,
panel = chart.panel,
yAxis = panel.yAxis,
bar = this.barFromPixel(this.cx);
if (this.chart.dataSegment[bar]) {
this.crossYActualPos = this.pixelFromPrice(this.chart.dataSegment[bar].Close, panel);
if (yAxis.bottom < this.crossYActualPos) this.crossYActualPos = yAxis.bottom;
this.controls.crossY.style.top = this.crossYActualPos + "px";
}
});
Smooth X crosshair movement
This example shows how to allow the x-axis crosshair to move freely between the bar interval instead of snapping to the center of the candle. Both injections are required.
// allow free movement of the crosshair
CIQ.ChartEngine.prototype.append("positionCrosshairsAtPointer", function() {
this.controls.crossX.style.left = this.cx + "px";
});
// allow free movement of the floating label
CIQ.ChartEngine.prototype.append("updateChartAccessories", function() {
var floatDate = this.controls.floatDate;
if (floatDate) {
var l = this.cx - floatDate.offsetWidth / 2 - 0.5;
if (l < 0) l = 0;
floatDate.style.left = l + "px";
}
});
Adding a highlight on the mouse-over bar
This injection will produce a vertical highlight over the bar of the current crosshair location, and display the x- and y-axis floating labels.
CIQ.ChartEngine.prototype.prepend("headsUpHR", function() {
var crossX = this.controls.crossX;
if (
this.currentVectorParameters.vectorType &&
this.currentVectorParameters.vectorType != "mouse-over-highlight"
) {
crossX.style.width = "";
crossX.style.backgroundColor = "";
return;
}
// include this if you want the highlight and floating labels to also show when crosshairs are off
if (!stxx.layout.crosshair) {
this.currentVectorParameters.vectorType = "mouse-over-highlight";
this.controls.crossY.style.display = "none";
} else {
this.currentVectorParameters.vectorType = "";
this.controls.crossY.style.display = "";
}
// end include
var left = CIQ.stripPX(crossX.style.left) - this.layout.candleWidth / 2;
var width = this.layout.candleWidth;
if (left + width > this.chart.width) {
// adjust width of last bar so it does not highlight past the edge of the chart into the y-axis
width = this.chart.width - left;
}
crossX.style.left = left + "px";
crossX.style.width = width + "px";
crossX.style.backgroundColor = "rgba(191, 191, 191, .2)";
});
Your chart will resemble something like this:
Toggle crosshair via a long touch
This combination injection/eventListener allows users on touch devices to enable the crosshair using a long touch gesture. The crosshair will remain enabled until the user is no longer touching the screen.
/**
* Enables the crosshair after a delay when the user is holding in one
* position. Designed for touch devices.
*
* @param {CIQ.ChartEngine} stx
* @param {Object} [options]
* @param {number} [options.enable=300] delay time in milliseconds, display on touchstart + delay
* @param {number} [options.disable=0] delay time in milliseconds, continue displaying crosshair after touchend + delay
* @example
* var stxx = new CIQ.ChartEngine({container: document.querySelector('.chart-container')});
* enableCrosshairViaLongTouch(stxx, {enable: 300, disable: 1800});
*/
function enableCrosshairViaLongTouch(stx, options) {
var layout = stx.layout;
var enable = (options && options.enable) || 300;
var disable = (options && options.disable) || 0;
var disableCrosshairs = function() {
layout.crosshair = false;
stx.undisplayCrosshairs();
};
stx.addEventListener("longhold", function (lhObject) {
stx.grabbingScreen=false;
stx.mousemoveinner(lhObject.x + stx.crosshairXOffset , lhObject.y );
layout.crosshair = true;
stx.doDisplayCrosshairs();
});
stx.append("touchend", function() {
if (layout.crosshair) {
var disableTimer = setTimeout(disableCrosshairs, disable);
}
});
}
Toggle crosshair via a double tap
This injection allows users on touch devices to toggle the crosshair using a double tap, which by default is used to delete plots.
function touchDoubleClickOverride(finger, x, y) {
if (this.dispatch("doubleTap", { stx: this, finger: finger, x: x, y: y })) return true;
if (x < this.left || x > this.right || y < this.chart.panel.top || y > this.chart.panel.bottom)
return;
if (this.editingAnnotation) return;
if (CIQ.ChartEngine.drawingLine) {
this.undo();
} else if (this.anyHighlighted) {
this.deleteHighlighted();
} else {
stxx.layout.crosshair = !stxx.layout.crosshair;
if (stxx.layout.crosshair) {
stxx.doDisplayCrosshairs();
} else {
stxx.undisplayCrosshairs();
}
}
return true;
}
stxx.prepend("touchDoubleClick", touchDoubleClickOverride);
If you want to prevent plots to be deleted with a double tap, see CIQ.ChartEngine#bypassRightClick
Disable crosshairs during drawing operations
By default, crosshairs are automatically enabled while in drawing mode. To override the default behavior and disable crosshairs during drawing operations, add the following injection to the crosshair display function:
CIQ.ChartEngine.prototype.prepend("doDisplayCrosshairs", function() {
if (this.displayInitialized && this.currentVectorParameters.vectorType) {
this.undisplayCrosshairs();
return true;
}
});
To maintain the floating labels on the axis, but just remove the line, use this instead:
CIQ.ChartEngine.prototype.append("headsUpHR", function() {
var controls = this.controls,
crossX = controls.crossX,
crossY = controls.crossY;
if (
this.displayInitialized &&
this.currentVectorParameters.vectorType &&
crossX &&
crossX.style.display != "none"
) {
crossX.style.display = "none";
crossY.style.display = "none";
}
});
Disable crosshair from automatically enabling during drawing operations
This injection will display a crosshair during drawing operations only if the main crosshair setting is enabled, rather than automatically enabling the crosshair whenever drawings are activated.
stxx.prepend("doDisplayCrosshairs", function() {
const { vectorType } = this.currentVectorParameters;
if ( !this.layout.crosshair && vectorType && vectorType != "") {
this.undisplayCrosshairs();
//console.log("don't show crosshair on drawings");
return true;
}
if ( this.layout.crosshair && vectorType == '') {
//console.log(" show crosshair on no-tool");
this.currentVectorParameters.vectorType = " ";
}
});
Menu manipulations
Adding a confirmation pop-up before deleting a highlighted item from the chart
Right clicking on highlighted items (such as overlays or drawings) deletes them. You may prefer to provide a confirmation dialog to your users. This can be accomplished by injecting a prepend to the "deleteHighlighted" function:
function prependDeleteHighlighted() {
if (confirm("Are you sure you want to delete this item?") != true) {
return true; // bypasses the core library code, preventing the deletion
}
}
CIQ.ChartEngine.prototype.prepend("deleteHighlighted", prependDeleteHighlighted);
Background Colors
Change the color of the main chart panel
This injection changes the background color of the main chart panel, excluding the axis. Note that we inject before the draw function using a prepend:
function drawBox() {
this.chart.context.fillStyle = "orange"; /// set your color here
this.chart.context.fillRect(
this.chart.panel.left,
0,
this.chart.panel.width,
this.chart.panel.yAxis.height
);
}
CIQ.ChartEngine.prototype.prepend("draw", drawBox);
Add a horizontal striped background
Here we inject on the "plotYAxisGrid" function in order to create an alternating background. plotYAxisGrid is used to draw the horizontal grid lines on a chart.
The function utilizes the data stored in the Plotter's cache. The "Plotter" conveniently contains the locations of grid lines. This injection iterates through the Plotter's line drawing instructions (lineTo, moveTo) to locate the starting x & y coordinates and corresponding offsets. It then renders rectangles that lay perfectly within alternating grid lines.
This code also handles the special case of rendering the very first highlight from the top, which at times could be of partial height:
function appendPlotYAxisGrid(panel) {
// Skip study panels
if (panel.name != "chart") return;
// set your alternate color. Add transparency so the grid lines are visible
this.chart.context.fillStyle = "rgba(46, 97, 178,.05)";
var plotter = panel.yAxisPlotter,
ctx = this.chart.context;
for (var i = 0; i < panel.yAxis.yAxisPlotter.seriesArray.length; i++) {
var series = panel.yAxis.yAxisPlotter.seriesArray[i];
if (series.name != "grid") continue;
var startx,
starty,
moves = series.moves;
for (var j = 0; j < moves.length; j++) {
var move = moves[j];
if (move.action == "moveTo") {
startx = move.x;
starty = move.y;
// if there are no more lines, we need to color the last partial grid
// (unless it is not exactly at the top of the screen)
if (j + 2 >= moves.length && starty) {
// last bar
if (moves.length > j + 1) {
ctx.fillRect(startx, starty, moves[j + 1].x - startx, (starty *= -1));
console.log(startx, starty, moves[j + 1].x - startx, (starty *= -1));
}
}
j++; //skip the next lineTo stored in the plotter cache
j++; //skip the next moveTo stored int he plotter cache
}
if (move.action == "lineTo") {
ctx.fillRect(startx, starty, move.x - startx, move.y - starty);
}
}
}
}
CIQ.ChartEngine.prototype.append("plotYAxisGrid", appendPlotYAxisGrid);
Add a vertical striped background
Here we inject on the appendDrawXAxis
function in order to create an alternating background. appendDrawXAxis is used to draw the vertical grid lines on a chart.
function appendDrawXAxis(chart, axisRepresentation) {
// Only do alternating shading if the x-axis at the bottom.
// Otherwise more code is needed to shade the panels under the axis without shading over the axis itself.
if (!this.xAxisAsFooter) return;
var panel = chart.panel;
if (panel.name != "chart") return; // Skip study panels
// set your alternate color. Add transparency so the grid lines are visible
this.chart.context.fillStyle = "rgba(46, 97, 178, .05)";
var ctx = this.chart.context;
var obj;
// find the bottom of the last y-axis on the chart.
// we need to know the y-axis because otherwise the bottom of the chart will be past the x-axis and we don't want that.
// subtract 1 because whichPanel is designed for displaying the x-axis also
var wPanel = this.whichPanel(this.chart.canvasHeight - 1);
if (!wPanel) return; // happens if window height increases during resize
var yAxis = wPanel.yAxis;
var bottom = yAxis.bottom;
var top = 0;
var prevRight = -1;
var nextBoundaryLeft = Number.MAX_VALUE;
var edges = [];
var edgesInterator = 0;
for (var nb = 0; nb < axisRepresentation.length; nb++) {
if (axisRepresentation[nb].grid == "boundary") {
nextBoundaryLeft = axisRepresentation[nb].left;
break;
}
}
for (var i = 0; i < axisRepresentation.length; i++) {
obj = axisRepresentation[i];
// Check for overlap
if (i == nb) {
for (nb++; nb < axisRepresentation.length; nb++) {
if (axisRepresentation[nb].grid == "boundary") {
nextBoundaryLeft = axisRepresentation[nb].left;
break;
}
}
if (nb >= axisRepresentation.length) {
// no more boundaries
nb = -1;
nextBoundaryLeft = Number.MAX_VALUE;
}
if (prevRight > -1) {
if (obj.left < prevRight) continue;
}
} else {
if (prevRight > -1) {
if (obj.left < prevRight) continue;
}
if (obj.right > nextBoundaryLeft) continue;
}
prevRight = obj.right;
if (Math.floor(obj.unpaddedRight) <= this.chart.right) {
// you may add more code here to have the shading follow the tick instead of
// always using the first grid line as the staring point which can cause some
// flickering and unnatural shifts of the alternating colors.
if (!edges[edgesInterator]) edges[edgesInterator] = { left: obj.hz };
else {
edges[edgesInterator].right = obj.hz - edges[edgesInterator].left;
edgesInterator++;
}
}
}
if (edges[edgesInterator] && edges[edgesInterator].left && !edges[edgesInterator].right) {
edges[edgesInterator].right = this.chart.right - edges[edgesInterator].left;
edgesInterator++;
}
for (var j = 0; j < edgesInterator; j++) {
if (edges[j].left && edges[j].right)
ctx.fillRect(edges[j].left, top, edges[j].right, bottom);
}
}
CIQ.ChartEngine.prototype.append("drawXAxis", appendDrawXAxis);
Your chart will resemble something like this:
Chart manipulations
Customizing colored line charts to include dashes on flat lines
The following will render dashed lines when there is no change or markets are closed/inactive causing a flat line on the chart. Similar customizations can be done for other chart types. See colorFunction
CIQ.ChartEngine.prototype.prepend("displayChart", function(chart) {
if (this.layout.chartType == "colored_line")
this.chart.customChart = {
colorFunction: function(stx, quote, mode) {
var stxLineUpColor = stx.getCanvasColor("stx_line_up");
var stxLineDownColor = stx.getCanvasColor("stx_line_down");
var stxLineColor = stx.getCanvasColor("stx_line_chart");
if (quote.Close > quote.iqPrevClose) return stxLineUpColor;
else if (quote.Close < quote.iqPrevClose) return stxLineDownColor;
else return { color: stxLineColor, pattern: [1, 3] };
}
};
else this.chart.customChart = null;
});
Your chart will resemble something like this:
Adding a mountain gradient to the candle chart
You can add a gradient to the candle chart by using a prepend to the "displayChart" function as follows:
CIQ.ChartEngine.prototype.prepend("displayChart", function() {
if (this.layout.chartType == "candle") {
this.startClip(this.chart.panel.name);
this.drawMountainChart(this.chart.panel, "custom_candle_mountain");
this.endClip();
}
});
Put this function call anywhere in your code after the library js files have loaded.
You will also need to have the following CSS entry:
.custom_candle_mountain {
background-color: rgba(255, 0, 0, 0.5); /* background color for mountain.*/
color: rgba(255, 0, 0, 0.01); /* Optional gradient.*/
}
Your chart will resemble something like this:
Force a specific width for a particular candle
This example sets the width of the 10th candle displayed on the view window (dataSegment) to a width of 100.
CIQ.ChartEngine.prototype.append("createDataSegment", function(){
var dataSegment=this.chart.dataSegment;
var quote;
for(var i=0;i< dataSegment.length;i++){
quote = dataSegment[i];
if( i == 10) quote.candleWidth=100;
}
});
Label manipulations
Adding an HTML object that remains nailed to primary chart panel
If you are trying to add a label that will remain attached to the main chart panel, as new study panels are opened or the size of the chart changes, here are the instructions.
First put a div for the disclaimer on your page:
<div class="stx-disclaimer"></div>
Then style it (in the CSS file):
.stx-disclaimer {
position: absolute;
bottom: 50px;
left: 10px;
display: block;
font-size: 13px;
color: #929292;
padding: 7px 12px 12px;
background: rgba(0, 0, 0, 0.05);
border-style: solid;
border-width: 1px;
width: 386px;
/*height: 45px;*/ /* let it adjust based on text length */
}
Add anything you want to the HTML to display your text, or dynamically add code in the JavaScript to set it like so:
document.querySelector(".stx-disclaimer").innerHTML = "Your text here";
Then add the following injection to move it as studies are added or the size of the window changes:
CIQ.ChartEngine.prototype.append("adjustPanelPositions", function() {
// this places and maintains the disclaimer 48 pixels above the bottom edge of the main chart panel
document.querySelector(".stx-disclaimer").style.bottom =
this.chart.canvasHeight - this.chart.panel.bottom + 48 + "px";
});
Gesture and mouse-click manipulations
Performing additional actions after a pinching gesture
The following injection will trigger once your pinching or reverse pinching gesture is completed. You can add code here to perform any functions you need upon completing a zoom action on a touch device. This injection may be useful for determining chart resolution for the purpose of resizing your custom on-screen elements.
Note that this will only trigger once your fingers move away from the screen and will not trigger while you continue to pinch in and out.
CIQ.ChartEngine.prototype.append("touchend", function() {
if (this.pinchingScreen > 1) {
alert("was zooming");
// add your code here to trigger any additional action once the zoom is completed on a touch device.
}
});
Disabling right-click to edit on drawings
Normally on “right-click”, a context menu with editing options will appear allowing the user to select a variety of different drawing editing features. This injection will disable this feature and immediately delete a drawing instead.
stxx.prepend("rightClickHighlighted", function(){
for (var i = this.drawingObjects.length - 1; i >= 0; i--) {
var drawing = this.drawingObjects[i];
if (drawing.highlighted){
this.deleteHighlighted(false);
return true;
}
}
});
Hover manipulations
Prevent hover highlights on series
Although not a bona fide injection, the idea is similar. This code can be used to prevent a series from being highlighted when mousing over it:
stxx.origPlotDataSegmentAsLine = stxx.plotDataSegmentAsLine;
stxx.plotDataSegmentAsLine = function(field, panel, parameters, colorFunction) {
parameters.highlight = false;
var rc = this.origPlotDataSegmentAsLine(field, panel, parameters, colorFunction);
return rc;
};